#include "mainwindow.h"
#include "ui_mainwindow.h"

//icones dans menu pour windows 7 --> need plugins/imageformats/libqico*.so/sll!
//voir .pro aussi ... faut etre en Qt4.8 pour avoir qico ??
//http://www.qtcentre.org/threads/28528-Action-Icons-not-showing-when-running-app
//#include <QtPlugin>
//Q_IMPORT_PLUGIN(qico)
//Q_IMPORT_PLUGIN(qjpeg)

#include<QDir>
#include<QSettings>
#include<QMessageBox>

#include <QPropertyAnimation>
#include <QParallelAnimationGroup>

// caractere de retour \n ou \r
#define RETURN_CHAR       "\n"   /* 0x0A */
#define RETURN_CHAR_STR     "0A"


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    portSerie(NULL)
{
    ui->setupUi(this);

    //remplir les combobox

    ui->cbx_baudrate->addItem("57600",BAUD57600);
    ui->cbx_baudrate->addItem("115200",BAUD115200);
#ifdef Q_OS_LINUX
    ui->cbx_baudrate->addItem("500000",BAUD500000);
#else
    ui->cbx_baudrate->addItem("500000",500000); //BAUD500000 pas defini pour windows soit disant non compatible
#endif

    // ENUMERATOR de port
    QList<QextPortInfo> listPorts = QextSerialEnumerator::getPorts();
    foreach(QextPortInfo portInfo, listPorts)
    {
        // toute la structure PortInfo
#ifdef Q_OS_LINUX
        ui->cbx_port->addItem(portInfo.friendName, QVariant(portInfo.physName) );
#else
        ui->cbx_port->addItem(portInfo.friendName, QVariant(portInfo.portName) );
#endif
        //affiche toutes pptes
        log( trUtf8("%1 %2 %3 %4 %5 : %6").arg(portInfo.portName,
                                               portInfo.physName,
                                               portInfo.friendName,
                                               portInfo.enumName,
                                               QString::number(portInfo.vendorID,16),
                                               QString::number(portInfo.productID,16) ));
        //"ttyS0 /dev/ttyS0 Serial port 0 /dev b6cb4160 : 0"
        //COM4 \Device\00000089 MOXA UPort COM Port 1 (COM4) MXUPSER 0 : 0
    }

    //pour detecter changement de nb de ports
    serialEnum = new QextSerialEnumerator();
    serialEnum->setUpNotifications(); // FAILED dans Vbox linux!!?? OK windows 7
    connect(serialEnum, SIGNAL(deviceDiscovered(const QextPortInfo &)), this, SLOT(onDeviceDiscovered(const QextPortInfo &)));
    connect(serialEnum, SIGNAL(deviceRemoved(const QextPortInfo &)), this, SLOT(onDeviceRemoved(const QextPortInfo &)));

    connect(ui->cbx_port    ,SIGNAL(currentIndexChanged(int)),this,SLOT(cbxportChanged(int)));
    connect(ui->cbx_baudrate,SIGNAL(currentIndexChanged(int)),this,SLOT(cbxBaudrateChanged(int)));

    connect(ui->le_inputDataHex,SIGNAL(textChanged(QString)),this,SLOT(storeData(QString)));

    //whan push enter on line edit do as pushing "envoyer" button
    connect(ui->le_inputDataHex,SIGNAL(returnPressed()),this,SLOT(on_pb_sendSerie_clicked()));

    //ajoute supprime 0x0a
    connect(ui->cb_Addreturn,SIGNAL(clicked()),this,SLOT(addln()));

    connect(ui->le_text,SIGNAL(textChanged(QString)),this,SLOT(convHex(QString)));

    connect(ui->le_decimalNumber,SIGNAL(textChanged(QString)),this,SLOT(convertDecToHex(QString)));

    //multi language
    languageActionGroup = new QActionGroup(this); //pour avoir un seul slot pour toutes les langues
    languageActionGroup->addAction(ui->actionAnglais);
    ui->actionAnglais->setData("en_GB"); //utilise cette donnee pour discriminer les langues et charger fichier
    ui->actionAnglais->setIconVisibleInMenu(true); //forcage show : marche pas, TODO FIXME : icones pas visible sous windows 7
    //ui->actionAnglais->setIcon(QIcon(":/icons/icons/en.jpg"));
    languageActionGroup->addAction(ui->actionFran_ais);
    ui->actionFran_ais->setData("fr_FR");
    ui->actionFran_ais->setIconVisibleInMenu(true); //forcage show : marche pas, TODO FIXME : icones pas visible sous windows 7
    //ui->actionFran_ais->setIcon(QIcon(":/icons/icons/fr.jpg"));
    languageActionGroup->setExclusive(true);
    connect(languageActionGroup, SIGNAL(triggered(QAction *)), this, SLOT(switchLanguage(QAction *)));

    //QSettings: essai de restaurer les parametres
    //on a ouvert un port, on sauvegarde
    QSettings settings("serialTerm.ini", QSettings::IniFormat);

    QString langue = settings.value("Application/langue").toString();
    if(langue.isEmpty()==false)
    {
        switchLanguage(langue);
        //QAction::setChecked(); de la langue
        QList<QAction *> listLangues = languageActionGroup->actions();
        foreach(QAction *actLangue, listLangues)
        {
            //essai de trouver la langue, doit normalement la trouver sinon c'est que le .ini est corrompu
            if( langue.contains( actLangue->data().toString()) == true)
            {
                actLangue->setChecked(true);
            }
        }
    }
    else
    {
        ui->actionFran_ais->setChecked(true);
    }

    QString port = settings.value("serial/port").toString();
    int findItem = ui->cbx_port->findData(port);
    if( findItem != -1)
    {
        //on a trouve indice: applique
        ui->cbx_port->setCurrentIndex(findItem);
    }

    int speed= settings.value("serial/speed").toInt();
    findItem = ui->cbx_baudrate->findData(speed);
    if( findItem != -1)
    {
        //on a trouve indice: applique
        ui->cbx_baudrate->setCurrentIndex(findItem);
    }

    //restaure position de l'ui, et si y'a pas de .ini ??
    restoreGeometry(settings.value("Application/geometry").toByteArray());
    restoreState(settings.value("Application/windowState").toByteArray());

    //init with debug
    ui->actionDebug->setChecked(true);
}



MainWindow::~MainWindow()
{
    if(portSerie != NULL)
    {
        portSerie->close();
        delete portSerie;
    }
    delete ui;
    delete serialEnum;
}


void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);

        if(ui->pb_openClosePort->isChecked() == true)
        {
            //label closed, this is a duplication of same label
            ui->pb_openClosePort->setText(trUtf8("Fermer port"));
        }
        else
        {
            //label open
            ui->pb_openClosePort->setText(trUtf8("Ouvrir port"));
        }

        break;
    default:
        break;
    }
}


void MainWindow::switchLanguage(QAction *action)
{
    QString locale = action->data().toString();
    switchLanguage(locale);
}

void MainWindow::switchLanguage(QString locale)
{
    log(locale);
    //languages files qm are embedded in resources
    if( appTranslator.load(":/intl/intl/ihmng_" + locale) == false)
    {
        log( trUtf8("[C] changement de langue non trouve") );
    }
    else
    {
        //applique la traduction immediatement
        qApp->installTranslator(&appTranslator);
    }
}


//
// SLOT Version informations
//
void MainWindow::on_actionA_propos_triggered(bool NotUsed)
{
    Q_UNUSED(NotUsed);
    QMessageBox::information(this, trUtf8("A propos ..."),trUtf8("Version : %1").arg( QString(SVNVERSION) ));
}


//
// log dans la fenetre de logs
//
void MainWindow::log(QString str)
{
    ui->te_debug->append(str);
}


//
// SLOT converti lineEdit decimal en chaine hexa
//
void MainWindow::convertDecToHex(QString str){
    bool ok;
    ui->lb_decInHex->setText(QString("0x%1").arg(QString::number(str.toLong(&ok,10),16)) );
}

//
// Slot converti text en chaine hexa
//
void MainWindow::convHex(QString str)
{
    QString out="0x";
    QByteArray strLCopy(str.toAscii());

    for(int i=0 ; i<strLCopy.size() ; ++i )
    {
        out.append( QString::number(strLCopy.at(i),16) );
    }

    ui->lb_hexa->setText(out);
}

//
// slot edit data to send
// str a  sauver dans QBA au bon format
void MainWindow::storeData(QString str)
{
    bool ok;
    QString str2char, strLCopy;
    unsigned char valHex;

    strLCopy = str.simplified(); //supprime espaces
    dataToSend.clear();

    //il faut convertir les char 2 par 2
    strLCopy.remove(' '); //retire tous les espaces restant
    for(int i=0 ; i<strLCopy.size() ; i+=2 )
    {
        str2char =strLCopy.mid(i/*pos*/,2/*nb char*/);
        valHex = str2char.toUInt(&ok,16);
        //dataToSend
        dataToSend[i/2] = valHex;
    }
/*
    log( trUtf8("storeData %1 bytes").arg(QString::number(dataToSend.size())) );
    for(int i=0; i<dataToSend.size();++i)
    {
        log( trUtf8("b%1 : 0x%2").arg(QString::number(i) , QString::number(dataToSend.at(i),16)) );
    }
*/
}

//
// slot click checkbox pour rafraichir affichage hexa avec ou sans \n
//
void MainWindow::addln(void)
{
    if( ui->cb_Addreturn->isChecked() )
    {
        ui->lb_ln->setText(RETURN_CHAR_STR);
    }
    else
    {
        ui->lb_ln->setText("");
    }
}

//
// slot read data
//
void MainWindow::onDataAvailable(void)
{
    int avail = portSerie->bytesAvailable();

    if (avail > 0)
    {
            QByteArray data;
            data.resize(avail);
            int read = portSerie->read(data.data(), data.size());
            if(read > 0)
            {
                log(trUtf8("Read %1 bytes").arg(QString::number(read)));
                ui->te_serial->append( data ); //pour le moment affiche direct
                //emit DataReceived(data); //pour faire le vrai traitement ailleur
            }
    }
}

//
// slot autoconnect envoi to CN
//
void MainWindow::on_pb_sendSerie_clicked(void)
{
    if(portSerie !=NULL){
        qint64 written = portSerie->write( dataToSend );
        if(written != dataToSend.size())
        {
            if(written !=0)
            {
                //TODO FIXME : quand renvoi 0 c'est que ca fonctionne bien ?????? pas dans la doc !! mais apparement le cas !!
                log(trUtf8("[W] Pas le compte envoye !! %1 au lieu de %2").arg(QString::number(written) , QString::number(dataToSend.size())) );
                log(portSerie->errorString());
                //todo fixme: oinvestiquer plus is arrive ici
            }
        }

        if( ui->cb_Addreturn->isChecked() )
        {
            //send "\n" en plus a la fin
             portSerie->write(RETURN_CHAR); // ou mettre '\r' ?
        }
    }
    else
    {
        log(trUtf8("[C] Port non ouvert!"));
    }
}



//
// slot changement de port
//
void MainWindow::cbxportChanged(int index)
{
    QString caption = ui->cbx_port->itemText(index); //index affiche
    QString data    = ui->cbx_port->itemData(index).toString(); // -> le port a  ouvrir

    log(trUtf8("Port choisi: \"%1\" %2").arg(caption, data));

    // TODO detecter si port precedent ouvert pour le fermer !!
}


//
// slot changement de vitesse
//
void MainWindow::cbxBaudrateChanged(int index)
{
    QString caption = ui->cbx_baudrate->itemText(index); //index affiche
    int data    = ui->cbx_baudrate->itemData(index).toInt(); // -> la vitesse selectionnee

    log(trUtf8("Vitesse choisie: \"%1\" %2").arg(caption, QString::number(data)));
}


//
// slot open/close autoconnect
//
void MainWindow::on_pb_openClosePort_clicked(void)
{
    QString portAOuvrir = ui->cbx_port->itemData(ui->cbx_port->currentIndex()).toString();

    if(portSerie == NULL){
        log(trUtf8("Ouvrir %1").arg(portAOuvrir));
        ui->pb_openClosePort->setText(trUtf8("Fermer port"));//bouton pret pour prochaine fermeture
        portSerie = new QextSerialPort( portAOuvrir ); //herite de QIODevice
        QObject::connect(portSerie, SIGNAL(readyRead()), this, SLOT(onDataAvailable()));
        if( portSerie->open(QIODevice::ReadWrite) == true) //eventuellement mettre QIODevice::Unbuffered pour eviter que soit lent !? ou flush reguliers
            {
            portSerie->setBaudRate( (BaudRateType)(ui->cbx_baudrate->itemData(ui->cbx_baudrate->currentIndex()).toInt()) );
            portSerie->setFlowControl(FLOW_OFF);
            portSerie->setParity(PAR_NONE);
            portSerie->setDataBits(DATA_8);
            portSerie->setStopBits(STOP_1);
            portSerie->setTimeout(50); // milliseconds, mais ca sert a  rien en eventdriven !! (normal)

            //on a ouvert un port, on sauvegarde
            QSettings settings("serialTerm.ini", QSettings::IniFormat);

            settings.beginGroup("serial");

            settings.setValue("port", portAOuvrir);
            settings.setValue("speed", (int)portSerie->baudRate() );
            settings.endGroup();

            settings.beginGroup("Application");
            settings.setValue("geometry", saveGeometry());
            settings.setValue("windowState", saveState());
            settings.setValue("langue", languageActionGroup->checkedAction()->data() );
            settings.endGroup();
        }
        else
        {
            log(trUtf8("[C] Ne peut ouvrir le port!"));
        }

    }
    else
    {
        //port deja  ouvert: le ferme et desalloue
        log(trUtf8("Fermer %1").arg(portSerie->portName()));
        ui->pb_openClosePort->setText(trUtf8("Ouvrir port"));//bouton pret pour prochaine ouverture
        portSerie->close();
        portSerie->disconnect(); //disconnect all
        delete portSerie;
        portSerie=NULL;
    }

}


void MainWindow::onDeviceDiscovered(const QextPortInfo &port)
{
    //ajouter a la liste (a  la fin)
#ifdef Q_OS_LINUX
    ui->cbx_port->addItem(port.friendName, QVariant(port.physName) );
#else
    ui->cbx_port->addItem(port.friendName, QVariant(port.portName) );
#endif
}


void MainWindow::onDeviceRemoved(const QextPortInfo &port)
{
    //rechercher dans la liste et suppr de la liste (si c'est celui en cours utilisation -> faire un close , delete...)
    // port.friendName -> itemText qstring
    // port.physName -> itemData   qvariant / portName en Windows !!

    int idx = ui->cbx_port->findText( port.friendName,  Qt::MatchExactly | Qt::MatchCaseSensitive );
    if(idx != -1)
    {
        //idx correspond a  l'indice dans le cbx trouve
        // peut checker que itemData == port.physName (TODO FIXME ?)

        if(idx == ui->cbx_port->currentIndex())
        {
            log( trUtf8("[C] Port %1 En cours d'usage removed !!").arg(port.friendName) );
            //todo fixme: gerer le close , delete .. si c'est ouvert
        }

        ui->cbx_port->removeItem(idx);
        log( trUtf8("Port %1 removed").arg(port.friendName) );
    }
    else
    {
        log(trUtf8("Port %s removed non retrouve dans la liste").arg(port.friendName));
    }
}

void MainWindow::on_actionDebug_triggered(bool status)
{
    QParallelAnimationGroup *group = new QParallelAnimationGroup;
    QPropertyAnimation *animation = new QPropertyAnimation(ui->te_debug, "geometry");
    QPropertyAnimation *animation2 = new QPropertyAnimation(ui->te_serial, "geometry");
    QRect geom;

    if(status == false)
    {
        //ui->te_debug->hide();
        animation->setDuration(300);
        geom = ui->te_debug->geometry();
        animation->setStartValue(geom);
        geom.setHeight(0);
        animation->setEndValue(geom);

        animation2->setDuration(300);
        geom = ui->te_serial->geometry();
        animation2->setStartValue(geom);
        geom.setHeight(geom.height()+80);
        animation2->setEndValue(geom);

        group->addAnimation(animation);
        group->addAnimation(animation2);

         group->start();
    }
    else
    {
        //ui->te_debug->show();
        animation->setDuration(300);
        geom = ui->te_debug->geometry();
        animation->setStartValue(geom);
        geom.setHeight(80);
        animation->setEndValue(geom);

        animation2->setDuration(300);
        geom = ui->te_serial->geometry();
        animation2->setStartValue(geom);
        geom.setHeight(geom.height()-80);
        animation2->setEndValue(geom);

        group->addAnimation(animation);
        group->addAnimation(animation2);

         group->start();
    }
}
